<template>
  <div class="w-tabs" :class="tabsClasses">
    <div class="w-tabs__bar" ref="tabsBarRef" :class="tabsBarClasses">
      <div class="w-tabs__bar-item" v-for="(item, i) in tabsItems" :key="i" :class="barItemClasses(item)"
        @click="!item._disabled && openTab(item)" @focus="$emit('focus', cleanTab(item))"
        :tabindex="!item._disabled && 0" @keypress.enter="!item._disabled && openTab(item)"
        :aria-selected="item._active ? 'true' : 'false'" role="tab">
        <slot v-if="$slots[`item-title.${item.id || i + 1}`]" :name="`item-title.${item.id || i + 1}`"
          :item="cleanTab(item)" :index="i + 1" :active="item._active"></slot>
        <slot v-else name="item-title" :item="cleanTab(item)" :index="i + 1" :active="item._active">
          <div v-html="item.title"></div>
        </slot>
      </div>
      <div class="w-tabs__slider" v-if="!noSlider && !card" :class="sliderColor" :style="sliderStyles"></div>
    </div>
    <div class="w-tabs__content-wrap" v-if="tabsItems.length">
      <transition :name="transitionName" :mode="transitionMode">
        <div class="w-tabs__content" v-if="activeTab" :key="activeTab._index" :class="contentClass">
          <slot v-if="
              $slots[`item-content.${activeTab.id || activeTab._index + 1}`]
            " :name="`item-content.${activeTab.id || activeTab._index + 1}`" :item="cleanTab(activeTab)"
            :index="activeTab._index + 1" :active="activeTab._active"></slot>
          <slot v-else name="item-content" :item="cleanTab(activeTab)" :index="activeTab._index + 1"
            :active="activeTab._active">
            <div v-html="activeTab.content"></div>
          </slot>
        </div>
      </transition>
    </div>
  </div>
</template>

<script>
  import {
    ref,
    reactive,
    computed,
    onBeforeMount,
    onBeforeUnmount,
    watch,
    nextTick,
  } from "vue";
  export default {
    name: "w-tabs",

    props: {
      modelValue: { type: Array },
      color: { type: String },
      bgColor: { type: String },
      items: { type: [Array, Number] },
      titleClass: { type: String },
      activeClass: { type: String, default: "primary" },
      noSlider: { type: Boolean },
      sliderColor: { type: String, default: "primary" },
      contentClass: { type: String },
      transition: { type: [String, Boolean], default: "" },
      fillBar: { type: Boolean },
      center: { type: Boolean },
      right: { type: Boolean },
      card: { type: Boolean },
    },

    emits: ["input", "update:modelValue", "focus"],
    setup(props, ctx) {
      const activeTabEl = ref(null);
      const prevTabIndex = ref(-1); // To detect transition direction.
      const activeTabIndex = ref(-1);
      const slider = reactive({
        left: 0,
        width: 0,
      });
      const init = ref(true);
      // refs
      const tabsBarRef = ref(null);

      const transitionName = computed(() => {
        if (props.transition === false) return "";
        return props.transition || `w-tabs-slide-${direction.value}`;
      });

      const transitionMode = computed(() => {
        return ["w-tabs-slide-left", "w-tabs-slide-right"].includes(
          transitionName.value
        )
          ? ""
          : "out-in";
      });

      const direction = computed(() => {
        return activeTab.value._index < prevTabIndex.value ? "right" : "left";
      });

      const tabsItems = computed(() => {
        const items =
          typeof props.items === "number"
            ? Array(props.items).fill({})
            : props.items;
        if (typeof props.items === "number" && activeTabIndex.value > -1) {
          // Array.fill({}) copies the same object by reference, make sure to modify only 1 by
          // giving a full object override (item = {}).
          if (items[activeTabIndex.value])
            items[activeTabIndex.value] = { _active: true };
        }
        return items.map((item, _index) =>
          reactive({
            ...item,
            _index,
            _active:
              item._active || (props.modelValue && props.modelValue[_index]),
            _disabled: !!item.disabled,
          })
        );
      });

      const activeTab = computed(() => {
        let activeTab = tabsItems.value.find((item) => item._active);
        if (!activeTab) {
          // If no active tab, open the first not disabled tab.
          if (!activeTab)
            activeTab = tabsItems.value.find((item) => !item._disabled);
          if (activeTab) {
            activeTab._active = true;
            nextTick(updateSlider);
          } else {
            activeTab = {}
          }
        }
        return activeTab;
      });

      const tabsClasses = computed(() => {
        return {
          "w-tabs--card": props.card,
          "w-tabs--no-slider": props.noSlider,
          "w-tabs--fill-bar": props.fillBar,
          "w-tabs--init": init.value,
        };
      });

      const tabsBarClasses = computed(() => {
        return {
          "justify-end": props.right,
          "justify-center": props.center,
        };
      });

      const sliderStyles = computed(() => {
        return {
          left: slider.left,
          width: slider.width,
        };
      });

      function onResize() {
        updateSlider(false);
      }

      function barItemClasses(item) {
        return {
          [`${props.bgColor}--bg`]: props.bgColor,
          [props.color]:
            props.color &&
            !item._disabled &&
            !(props.activeClass && item._active),
          [`w-tabs__bar-item--active ${props.activeClass}`]: item._active,
          "w-tabs__bar-item--disabled": item._disabled,
          [props.titleClass]: props.titleClass,
        };
      }

      function openTab(item) {
        prevTabIndex.value = activeTab.value._index;
        activeTabIndex.value = item._index;

        item._active = true;
        // Unset active on other tabs.
        tabsItems.value.forEach(
          (obj) => obj._index !== item._index && (obj._active = false)
        );
        const activeItem = tabsItems.value.map((item) => item._active);
        ctx.emit("update:modelValue", activeItem);
        ctx.emit("input", activeItem);

        if (!props.noSlider) {
          nextTick(updateSlider);
        }
      }

      function updateSlider(domLookup = true) {
        if (domLookup) {
          activeTabEl.value = tabsBarRef.value.querySelector(
            ".w-tabs__bar-item--active"
          );
        }

        if (!props.fillBar && activeTabEl.value) {
          const { left, width } = activeTabEl.value.getBoundingClientRect();
          const {
            left: parentLeft,
          } = activeTabEl.value.parentNode.getBoundingClientRect();
          slider.left = `${left - parentLeft + activeTabEl.value.parentNode.scrollLeft
            }px`;
          slider.width = `${width}px`;
        } else {
          (slider.left = `${(activeTab.value._index * 100) / tabsItems.value.length
            }%`),
            (slider.width = `${100 / tabsItems.value.length}%`);
        }
      }

      function cleanTab(tab) {
        // eslint-disable-next-line no-unused-vars
        const { _index, _expanded, _disabled, ...Tab } = tab;
        return Tab;
      }

      onBeforeMount(() => {
        nextTick(() => {
          updateSlider();
          // Disable the slider transition while loading.
          setTimeout(() => (init.value = false), 0); // Next tick is not sufficient here.
        });

        if (!props.noSlider) {
          window.addEventListener("resize", onResize);
        }
      });
      onBeforeUnmount(() => {
        window.removeEventListener("resize", onResize);
      });
      watch(
        () => props.modelValue,
        (array) => {
          tabsItems.value.forEach((item, i) => {
            item._active = (Array.isArray(array) && array[i]) || false;
          });
        }
      );

      watch(
        () => props.items,
        () => {
          if (!props.noSlider) {
            nextTick(updateSlider);
          }
        }
      );

      watch(
        () => props.fillBar,
        () => {
          if (!props.noSlider) {
            nextTick(updateSlider);
          }
        }
      );

      watch(
        () => props.noSlider,
        (value) => {
          if (!value) {
            updateSlider();
            window.addEventListener("resize", onResize);
          } else {
            window.removeEventListener("resize", onResize);
          }
        }
      );

      return {
        activeTabEl,
        prevTabIndex,
        activeTabIndex,
        slider,
        init,
        // refs
        tabsBarRef,
        // computed
        transitionName,
        transitionMode,
        direction,
        tabsItems,
        activeTab,
        tabsClasses,
        tabsBarClasses,
        sliderStyles,
        // methods
        onResize,
        barItemClasses,
        openTab,
        updateSlider,
        cleanTab,
      };
    },
  };
</script>